'''
Created on Jan 24, 2011

@author: dexen
'''

from dexen.system.common import meta
from dexen.system.common.meta import ScoreMeta
from dexen.system.common.excepts import ScoreNotExistsException,\
    ScoreAlreadyExistsException

MINIMIZE = meta.MINIMIZE
MAXIMIZE = meta.MAXIMIZE

class RankingResult(object):
    def __init__(self, ind, rank):
        self.ind = ind
        self.rank = rank
        

class ParetoRanking(object):
    def __init__(self, inds=[]):
        self.inds = inds
        self.scores = {}
        self.unranked = []
    
    def set_inds(self, inds):
        self.inds = inds
     
    def register_score(self, name, type, label=None):
        assert type == meta.MINIMIZE or type == meta.MAXIMIZE
        if self.scores.has_key(name):
            raise ScoreAlreadyExistsException("The score %s already exists"%name)
        self.scores[name] = ScoreMeta(name, type, label)

    def deregister_score(self, name):
        if self.scores.has_key(name):
            del self.scores[name]
        else: 
            raise ScoreNotExistsException("The score %s doesn't exist"%name)

    def get_scores(self):
        return self.scores.values()

    def _dominates(self, ind1, ind2):
        equal = True
        for score in self.scores.values():
            assert isinstance(score, ScoreMeta)
            val1 = getattr(ind1, score.name)
            val2 = getattr(ind2, score.name)
            if val1 != val2: equal = False
            if score.is_minimize():
                if val2 < val1:
                    return False
            elif val2 > val1:
                return False
        if equal: return False
        return True
    
    def _on_pareto_front(self, ind, inds):
        for ind2 in inds:
            if self._dominates(ind2, ind):
                return False
        return True

    def _is_fully_evaluated(self, ind):
        for name in self.scores.keys():
            if not hasattr(ind, name):
                return False
            if not getattr(ind, name):
                return False
        return True

    def _extract_pareto_front(self, inds):
        pareto_front = []
        non_pareto_front = []
        
        for ind in inds:    
            if self._on_pareto_front(ind, inds):
                pareto_front.append(ind)
            else:
                non_pareto_front.append(ind)
        return pareto_front, non_pareto_front            

    def rank(self, max_level=None):
        rank = 1
        cnt = 0
        ranked = []        
        inds = []
        for ind in self.inds:    
            if self._is_fully_evaluated(ind):
                inds.append(ind)
        while len(inds) > 0:
            pareto_front, non_pareto_front = self._extract_pareto_front(inds)
            print "pareto", len(pareto_front), "non pareto", len(non_pareto_front)
            for ind in pareto_front:
                ranked.append(RankingResult(ind, rank))
            inds = non_pareto_front
            cnt +=1
            rank += 1
            if max_level and cnt == max_level: break
        self.unranked = inds
        return ranked
    
    def get_unranked(self):
        return self.unranked
    
